// Notice
//  
// The contents of this file are subject to the
// same license as the source code for ImageJ.
// FracLac is in the public domain.  
// If you use it to publish research, please cite the source.
// The initial developer of the original 
// code is Audrey Karperien, Charles Sturt University.
package fraclac.FLUtilities;
   
import java.util.Map;

import javax.swing.JOptionPane;
  
   
/**Calculates basic statistics for
 *arrays of floats or integers and probability density distributions
 *for arrays of positive integers. Uses 0 as lower limit; largest
 *value in array as upper limit for binned distributions.
 * <h3>Inputs:</h3>
 * <ul><LI> array of floats or integers </LI>
 * <LI>integer for number of elements to use from this array</LI></ul>
 * <h3>Outputs:</h3>  <ul><LI>floats that are accessible by instantiating
 * this class;
 * <LI>Variance, </LI><LI>mean</LI>, <LI>coefficient of
 * variation,</LI> <LI>and standard deviation</LI> of
 * an array as a sample as well as <LI>probability distribution
 * for a passed array of integers</LI>.</LI></ul>
 * <h3>Structure:</h3>   Default constructor does nothing.  Other
 * constructors call {@link #CalculateUsingInternalVariables calculations}
 * automatically
 * which sets variables ({@link #stddev}, {@link #mean}, {@link #coefVar},
 *{@link #variance}). Must be instantiated and then
 * {@link #probabilityDistribution}
 * method must be called to return probability distribution.
 *
 *<ul><LI></p>
 *file:  statistics.java
 *
 * @version see FracLac_2.3
 * @author Audrey Karperien Charles Sturt University
 */
public class FLstatistics 
{ 
	/*
    public static void main(String []args)
    {
        int array []={60,
        60,
        60,60,75,75,75,75,85,85};
        float [] onearray=new float[14006];
        for (int i = 0; i < onearray.length; i++)onearray[i]=1;
         FLstatistics fs=new FLstatistics(onearray, onearray.length);
                 float s = fs.stddev;
        float a=LacunarityVariance(array, array.length);
        float b = (float)Math.sqrt(a);
        float c = LacunarityMean(array, array.length);
         System.out.println("Mean "+c);
         System.out.println("Standard Deviation " +b);
         System.out.println("Variance " +a);
         System.out.println("stdev "+s);
    }
    */
    
    //float num;
    
    /**Standard deviation = &#8730;({@link #variance})*/
    public float stddev = 0f;
    /**Coefficient of variation={@link #stddev}/{@link #mean}*/
    public float coefVar = 0f;
    /**variance = (&#8721;X<sup>2</sup> - (&#8721;X)<sup>2</sup>/N)/(N-1)*/
    /**variance = (&#8721;X<sup>2</sup> - (&#8721;X)<sup>2</sup>/N)/(N)*/
    private float variance = 0f;
    /**mean = &#8721;x/N (N=number of x values)*/
    public float mean = 0f;
  
    /**Constructor accepts no arguments; instantiates only.*/
    public FLstatistics()
    {};
    /** Constructor takes array to test and number of values to test.
     * Calls {@link #CalculateUsingInternalVariables calculate} method.
     * @param XArray float[] array of floats to assess
     * @param NUM integer for number of values to use from array
     */
    public FLstatistics(float[] XArray, int NUM) 
    {
        float smx=0f;
        
        for (int i = 0; i < NUM; i++)
        {
            smx=smx+XArray[i];
        }
        CalculateUsingInternalVariables(XArray, smx, NUM);
    }
    
    /** Constructor takes array to test and number of values to test.
     * Calls {@link #CalculateUsingInternalVariables calculate} method.
     * @param xint array of integers to assess
     * @param NUM integer for number of values to use from array
     */
    public FLstatistics(int[] xint, int NUM)
    {
        float [] Xfloat=new float[NUM];
        float sumx=0f;
        for (int i = 0; i < NUM; i++)
        {
            Xfloat[i]=(float)xint[i];
            sumx=sumx+Xfloat[i];
            
        }
        
        //float num=(float)NUM;
        CalculateUsingInternalVariables(Xfloat, sumx, NUM);
    }
    
    /** Calculates statistics on passed array
     * using previously set number (see Constructors)
     * @param SUMX float for the sum of the array
     * @param num integer for the number of elements to assess
     * @param x array of floats to assess
     */
    private void CalculateUsingInternalVariables(float[] x, float SUMX, int num)
    {
        float sumxsq=0f;
        float N = (float)num;
        
        for (int i =0;i<num; i++)
        {
            //sumx=sumx+(float)x[i];
            sumxsq=sumxsq+(x[i]*x[i]);
            
        }
        variance =   ((sumxsq-((SUMX*SUMX)/N))/(N));
        variance =   ((sumxsq-((SUMX*SUMX)/N))/(N));
        if(!(variance>=0))variance=0f;
        stddev = (float)Math.sqrt(variance);
        //if(!(stddev>=0))IJ.showMessage("shere "+stddev);
        mean = SUMX/N;
        coefVar=stddev/mean;       
    }
    
    
    
    //////////////////////////////////////////////////////////////////////////
    /** Returns the coefficient of variation, CV, of the passed array, as
     * the {@link #StdDev &sigma;}/{@link #Mean &mu;}.
     * @param x [] floats to assess
     * @param n int for number of elements in x to assess
     * @return float for coefficient of variation
     */ 
    public static float CoefVar(float[] x, int n)
    {
        float [] LV = Calc(x, n);
        return LV[3];  
        
        
    }
    
    
    
    
    
    
    ////////////////////////////////////////////////////////////////////////// 
    /**
     * Returns the probability density lacunarity, i.e., the
     * coefficient of variation, from an array of
     * box counting masses.
     * <LI>The value returned is from index 3 of {@link #CalcLacVar lacvar}.
     *
     * @param m int array of masses (pixels per box at an &epsilon;)
     * @param n int for the number of values to assess
     * @return float for the cv lacunarity
     */    
    public static float LacunarityCoefficientofVariation(int[] m, int n) 
    {
        float [] M = new float[n];
        for (int i = 0; i < n; i++)M[i]=(float)m[i];
        float [] LV = CalcLacVar(M, n);
        return LV[3];       
        
    }
    
    
    
    
    
    
    private static float[] Calc (float[] M, int n)
    {
        float SumMsq=0;
        float SumM=0;
        //float SumMM=0;
        //float SumPsq=0;
        float N = (float)n;
        
        for (int i =0;i<n; i++)
        {
            SumM=SumM+M[i];
            SumMsq=SumMsq+(M[i]*M[i]);
            
        }
        /*
        for (int i = 0; i < n; i++)
        {
            SumMM = (M[i]*M[i])*(M[i]/SumM);
        }
        */
        
        float [] vals = new float [4];
        
        float Variance =((SumMsq-((SumM*SumM)/N))/(N));
        //float Variance =((SumMsq-((SumM*SumM)/N))/(N-1));
        vals[0]=Variance;        
        
        float Stddev =(float) Math.sqrt(Variance);
        vals[1]=Stddev;
        
        float Mean = SumM/N;
        vals[2] = Mean; 
        
        float CoefVar=Stddev/Mean;
        vals[3]=CoefVar;        
        
        return vals;
        
        
    }
    /**
     * Calls {@link #probabilityDistributionMap method} to find then
     * calculates statistics for the probability distribution.
     *<LI>CV=&mu;/&sigma;</LI>
     *<LI>&mu;=&Sigma;E(x)=&Sigma;(Mass*Probability(Mass))</LI>
     *<LI>&sigma;<sup>2</sup>=&Sigma;((Mass-&mu;)<sup>2</sup>*Probability(Mass))
     * @param MM int [] of mass values to assess
     * @param n int for number of values to assess
     * @return float array of the standard deviation, mean, coefficient
     * of variation, and lacunarity.
     *@throws OutOfMemoryError
     */
    private static float[] CalcLacVar (float[] MM, int n)
    throws OutOfMemoryError
    {

        float PSum=0;
       float [][] MP={{1}, {1}}; 
       try
        {MP= probabilityDistributionMap(MM, n);
            
         
        } 
        catch (OutOfMemoryError oe) 
        {
            JOptionPane.showMessageDialog(
            null, "Out of Memory Calculating Lacunarity");
           
        } 
        
        float [] Mass = new float [MP[0].length];
        
        for (int i = 0; i < MP[1].length; i++)
        {
            PSum=PSum+MP[1][i];
            Mass[i]=(float)MP[0][i];
        }
        
        
        float Prob[]=new float [MP[0].length];
        for (int i = 0; i < MP[0].length; i++)
        {
            Prob[i]=((float)MP[1][i])/PSum;
        }
        
        float Variance = 0;
        
        float mean = 0;
        for (int i =0; i<Mass.length; i++)
        {
            mean = mean + (Mass[i]*Prob[i]);
        }
        
        for (int i = 0; i < Mass.length; i++)
        {
            Variance = Variance + (((Mass[i]-mean)*(Mass[i]-mean))*(Prob[i]));
            
        }
        
        float [] vals = new float [4];
        
        
        vals[0]=Variance;
        
        
        float Stddev = (float)Math.sqrt(Variance);
        vals[1]=Stddev;
        
        
        vals[2] = mean; 
        
        float CoefVar=Stddev/mean;
        vals[3]=CoefVar;
        
//        float lacVar= (SumPsq-(mean*mean))/(mean*mean);
//        vals[4] = lacVar;
        return vals;
        
        
    }
    
    
    
    
    /** 
     * Makes a probability distribution that is as long as the
     * largest value in the array passed+1.  First, every value
     * in the new array is set to 0.  Then, the number of occurrences
     * of each value in the old array is stored in the new array
     * at the position corresponding to the integer value in the array.
     * Thus, the array {3,4,4,3,3,2,1} is stored as {0,1,1,3,2}. Then, this
     * array is divided by the total number of elements, so the
     * final result is {0, 1/7, 1/7, 3/7, 2/7}.
     * 
     * @param array int [] array to arrange
     * @param NumElements int for the number of elements to assess
     * @return float [] of probabilities as described above
     */   
    /*
    private static float[] probabilityDistribution(int[] array, int NumElements)
    {   
        float [] probabilitydistribution=null;
        if (array.length>0)
        {
            
            //int numberOfBoxesWithPixels=boxes;NumElements now
            int BiggestNumber=array[0];
            for (int i = 0; i<NumElements; i++)
            {
                BiggestNumber
                =Math.max(BiggestNumber, array[i]);
            }
            
            probabilitydistribution = new float[BiggestNumber+1];
            
            for (int i = 0; i<probabilitydistribution.length; i++)
                probabilitydistribution[i]=0;
            for ( int a = 0;a < NumElements; a++)
            {
                if (true)
                {
                    float counter=0f;
                    for (int i = 0; i < NumElements; i++)
                    {
                        if (array[a] == array[i])counter++;
                    }
                    probabilitydistribution[array[a]]=counter;
                    
                }
            }
            
            for (int i = 0; i < probabilitydistribution.length; i++)
            {   probabilitydistribution[i] 
                = probabilitydistribution[i]/(float)NumElements;
            }
        }//end if the array was not empty
        return probabilitydistribution;
        
    }
    */
    
    /** 
     * Makes a probability distribution.  First, every value
     * in the new array is set to 0.  Then, the number of occurrences
     * of each value in the old array is stored in the new array
     * at the position corresponding to the integer value in the array.
     * Thus, the array {3,4,4,3,3,2,1} is stored as {0,1,1,3,2}. Then, this
     * array is divided by the total number of elements, so the
     * final result is {0, 1/7, 1/7, 3/7, 2/7}.
     * 
     * @param array int [] array to arrange
     * @param NumElements int for the number of elements to assess
     * @return float [] of probabilities as described above
     */   
    private static float [][] probabilityDistributionMap(float[] array,
    int NumElements)
    {   
        float [][] probabilitydistribution=null;
        int [] M = new int [NumElements];
        if (array.length>0)
        {
            
//            //int numberOfBoxesWithPixels=boxes;NumElements now
//            int BiggestNumber=array[0];
            for (int i = 0; i<NumElements; i++)
            {
                M[i]=(int)array[i];
            }
            
            probabilitydistribution = GetFrequencyTable(M);
            
            //find the smallest integer
            //count its occurences
            //store this value at the first position and store
            //the value at its
        }//end if the array was not empty
        return probabilitydistribution;
        
    }
    
    
    
    /**Returns a map that is a frequency distribution of the passed array
     *of integers.
     * @param args int array to arrange
     * @return a java.util.Map that maps the values in args to the number
     * of times they appear
     */    
    @SuppressWarnings({ "rawtypes", "unchecked" })
	private static java.util.Map LoadFrequencyTable(int args[])
    {
        final Integer ONE = new Integer(1);
        Integer ARGS [] = new Integer[args.length];
        for (int i = 0; i < args.length ; i++)
            ARGS[i]=new Integer(args[i]);
        
        
        java.util.Map m = new java.util.HashMap();
        
        for (int i=0; i<args.length; i++)
        {
            Integer freq = (Integer) m.get(ARGS[i]);
            try{m.put(ARGS[i], (freq==null ? ONE :
                new Integer(freq.intValue() + 1)));}            
            catch(UnsupportedOperationException uu){}
            catch(ClassCastException ff){}
            catch(NullPointerException rr){}
            catch(IllegalArgumentException rr){}
            catch(IndexOutOfBoundsException ee){}
        }
        
        //System.out.println(m.size()+" distinct detected:");
        //System.out.println(m);
        return m;
    }
    /**Returns an int [][] from the passed arguments by
     *calling {@link #ReadFrequencyTable reading} method
     *passing it the {@link #LoadFrequencyTable loading} method
     *passed args. The returned array has keys at [0] and values at [1].
     *The keys are the , and the values are the masses.
     * @param args int[] of values to arrange
     * @return int [][]
     */    
    private static float [][] GetFrequencyTable(int args[])
    {
        //Map m = LoadFrequencyTable(args);
        return ReadFrequencyTable(LoadFrequencyTable(args));
    }
    /**Returns an integer [][] of keys and values from the passed Map.
     * @param m a java.util.Map
     * @return int [][] of [keys][values]
     */    
    @SuppressWarnings("rawtypes")
	private static float[][] ReadFrequencyTable(java.util.Map m)
    {
        Integer [] KEYS = new Integer [m.size()];
        Integer [] VALUES = new Integer[m.size()];
        int c = 0;
        for (java.util.Iterator i=m.entrySet().iterator(); i.hasNext(); )
        {
            Map.Entry e = (Map.Entry) i.next();
            KEYS[c]=(Integer)e.getKey();
            VALUES[c]=(Integer)e.getValue();
            c++;
        }
        
        float [] Keys = new float [c];
        float [] Values = new float [c];
        for (int i = 0; i < m.size(); i++)
        {
            Keys[i]=KEYS[i].floatValue();
            Values[i]=VALUES[i].floatValue();
        }
        return new float[][]
        {Keys, Values};
        
    }
    
    
    
    
    
    
    /**returns the sum of the elements in the passed array, up
     *to the number passed.
     *
     *@param array int [] array to sum
     *@param Nums int for number of elements to sum
     *@return float for sum of all elements*/
    /*
    private static float SumArray(int [] array, int Nums)
    {
        float sum=0;
        for (int i =0; i < Nums; i++)sum=sum+array[i];
        return sum;
    }
    */
    
    
    
    
    
    /** Returns the probability 
     * distribution of a passed array of positive integers.
     * Assumes minimum of 1. Counts the number of times each number
     * occurs in the entire array and stores the count for each
     * number at the corresponding position in a new array.
     * Divides each element by the passed number of counts to make an array
     * of the probability of each element in the original array
     * @return float array probability distribution of passed array
     * @param bins int for the number of bins to divide the distribution into
     * @param array array of integers to be ordered
     * @param NumElements number of elements of the passed array to consider
     */
    public static float[][] binnedProbabilityDistribution(
    int [] array, int NumElements, int bins)
    {   int count =0;
        
        for (int i = 0; i <NumElements; i++)
        {
            if (array[i]>0)count++;
        }
        
        float [] temparray = new float [count];
        int w = 0;
        for (int i = 0; i < NumElements; i++)
        {
            if (array[i]>0)
            {
                temparray[w]=array[i];
                w++;
            }
        }
        //NumElements = count;
        // if (count<bins)bins=count/15;
        float [][] newarray = null;
        // if (bins<15)bins=15;
        
        double fdd=0;
        if (temparray.length==0)return(null);
        if (count==0)return(null);
        else
        {
            //int numberOfBoxesWithPixels=boxes;NumElements now
            float BiggestNumber=array[0];
            
            for (int i = 0; i<count; i++)
            {
                BiggestNumber=Math.max(BiggestNumber, temparray[i]);
            }
            if ((int)BiggestNumber<bins)bins=(int)BiggestNumber;
            int binsize = (int)BiggestNumber/bins;
            if(binsize<1)binsize=1;
            float halfa = (float)binsize/2;
            float BINS[]= new float[bins];
            BINS[0]=(halfa>0)?halfa: 1;
            for (int j = 1; j<(BINS.length); j++)
                BINS[j]=BINS[j-1]+binsize;
            int [] counts = new int [BINS.length];
            debug(344);//tally
            for (int i = 0; i < count; i++)
            {
                for (int b = 0; b < BINS.length;b++)
                {
                    {if (b>BINS.length-2)
                     {counts[b]++;b=BINS.length;}
                     else if (temparray[i]<BINS[b]+halfa)
                     {counts[b]++;b=BINS.length;}
                    }}}
            
            float []Prob=new float[counts.length];
            for (int i = 0; i < counts.length; i++)
            {
                Prob[i]=(float)counts[i]/(float)(count);
            }
            
            newarray = new float [2][BINS.length];
            for (int i = 0; i < BINS.length; i++)
            {
                newarray[0][i]=BINS[i];
                //System.out.println(BINS[i]);
                
                newarray[1][i]=Prob[i];
                fdd=fdd+Prob[i];
                //System.out.println(Prob[i]);
            }
        }//end if the array was not empty
        //System.out.println(fdd);
        return newarray;
        
    }
    
    private static void debug(int s)
    {
       // if (debug)IJ.log("Statistics" + Integer.toString(s));
    }
    
    
    

    
    
    
    
    
    
}//end class statistics

